#pragma once
#ifndef _KH_ENGINE_CLIENT_H
#define _KH_ENGINE_CLIENT_H
#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Units.h>
#include <Kharlia/Engine/Drawable.h>
#include <CEGUIInputEvent.h>
#include <CEGUIcolour.h>
#include <CEGUIDefaultResourceProvider.h>
#include <CEGUIScriptModule.h>
#include <CEGUIEventSet.h>
#include <unordered_map>

#ifdef CreateWindow
#undef CreateWindow // From Windows headers
#endif

namespace kharlia { namespace engine {

class CActor;
class ACamera;
class ARender;
class CEngine;
class CLevel;
class CClient;
class FTileLayer;
class FTileset;
class FRenderView;
class FTexture;

enum EBufferType {
    BUFFER_TYPE_NONE,
    BUFFER_TYPE_VBO
};

struct FVertex {
    D3DXVECTOR3 Pos;
    D3DXVECTOR2 TexCoord;

    FVertex() {}
    FVertex(D3DXVECTOR3 const& p): Pos(p) {}
    FVertex(D3DXVECTOR3 const& p,
            D3DXVECTOR2 const& t): Pos(p), TexCoord(t) {}
};

struct FBufferInfo {
    bool Valid;
    EBufferType Type;

    FBufferInfo(): Valid(false), Type(BUFFER_TYPE_NONE) {}
};

class KH_ENGINE_API FResourceProvider: public ui::DefaultResourceProvider {
    shared_ptr<core::FLogger> mLogger;
public:

    FResourceProvider(shared_ptr<core::FLogger> l);
    ~FResourceProvider() {}
    virtual void loadRawDataContainer(ui::String const& filename,
                                      ui::RawDataContainer& output,
                                      ui::String const& resourceGroup);
    virtual void unloadRawDataContainer(ui::RawDataContainer& data);
};

class KH_ENGINE_API CUIScriptModule: public core::CObject, public ui::ScriptModule {
public:
    CUIScriptModule(PyObject*);
    virtual ~CUIScriptModule() {}

    object GetBoundCallable(object name) {
        return call_method<object>(mSelf, "GetBoundCallable", name);
    }

    // ScriptModule interface
    virtual void executeScriptFile(ui::String const& filename, ui::String const& resourceGroup = "") {}
    virtual int executeScriptGlobal(ui::String const& functionName) { return 0; }
    virtual bool executeScriptedEventHandler(ui::String const& hname, ui::EventArgs const& e);
    virtual void executeString(ui::String const& s) {}
    virtual void createBindings();
    virtual void destroyBindings();
    virtual ui::Event::Connection subscribeEvent(
        ui::EventSet* target,
        ui::String const& name,
        ui::String const& subname
        );
    virtual ui::Event::Connection subscribeEvent(
        ui::EventSet* target,
        ui::String const& name,
        ui::Event::Group group,
        ui::String const& subname
        );
};

struct _FSizeIHash {
    size_t operator()(const FSizeI& s) const {
        std::hash<int> h;
        return h(s.Width) ^ h(s.Height);
    }
};

class KH_ENGINE_API CClient: public core::CObject {
public:
    static const uint VERTEX_BUFFER_SIZE = 64;

private:
    shared_ptr<core::FLogger> mLogger;
    TWeakPtr<CEngine> mEngine;
    object mConfig;
    bool mInited;
    FVector2I mMousePos;
    TWeakPtr<ACamera> mCamera;
    object mViewport;
    uint32 mWindowHandle;
    FSizeI mViewportSize;
    double mTime;
    bool mIsLeftArrowPressed;
    bool mIsRightArrowPressed;
    bool mIsUpArrowPressed;
    bool mIsDownArrowPressed;
    bool mKeyPressTable[256];
    std::vector<weak_ptr<FTexture>> mLoadedTextures;

    // GUI
    bool mInitedUI;
    bool mUIFocused;
    ui::Renderer* mUIRenderer;
    ui::System* mUISystem;
    FResourceProvider* mUIResourceProvider;
    ui::WindowManager* mUIWindowManager;
    ui::SchemeManager* mUISchemeManager;
    ui::FontManager* mUIFontManager;
    std::vector<FRenderView*> mRenderViews;
    ui::Window* mDebugText;
    ui::Window* mConsole;

    bool mInitedDX;
    bool mRendering;
    bool mShowActorBounds;
    bool mShowAllActors;
    bool mForceDefaultSprite;
    HDC mDeviceContext; // Device context handle from the window
    HGLRC mRenderingContext; // OpenGL rendering context handle
    ui::Texture* mErrorTexture;
    ui::Texture* mTilesetTexture;

    // Rendering implementation
    ID3D10Device1* mDevice;
    ID3D10RenderTargetView* mRTV;
    IDXGISwapChain* mSwapChain;
    ID3D10RasterizerState* mRS;
    ID3DXMatrixStack* mViewMatrixStack;
    ID3DXMatrixStack* mProjectionMatrixStack;
    ID3D10Effect* mBasicEffect;
    ID3D10InputLayout* mVertexLayout;
    ID3D10Buffer* mVertexBuffer;

    // 
    ID3D10EffectTechnique* mFullTechnique;
    ID3D10EffectTechnique* mNoTexTechnique;
    ID3D10EffectMatrixVariable* mWorldMatrixEV;
    ID3D10EffectMatrixVariable* mProjectionMatrixEV;
    ID3D10EffectMatrixVariable* mViewMatrixEV;
    ID3D10EffectVectorVariable* mGlobalColorEV;
    ID3D10EffectShaderResourceVariable* mTextureSR;

    std::unordered_map<FSizeI, ID3D10Buffer*, _FSizeIHash> mTileVertexBuffers;

    void InitUI();
    void ExitUI();
    void InitDX();
    void ExitDX();
    void ReleaseDX();
    void ResizeDX(FSizeI const& size);
    void SetDXViewport(FSizeI const& size);
    void InitRenderTarget();
    void OnUIExiting() { call_method<void>(mSelf, "_OnUIExiting"); }
    void OnPreRender() { call_method<void>(mSelf, "_OnPreRender"); }

    void DrawScene(TPtr<ACamera> camera, FRectF const& vp);
    void DrawLevelActors(CLevel*, int, float, float, float, float);
    void DrawActor(CActor*);
    void DrawTileLayer(FTileLayer*, ui::Texture*, D3DXCOLOR&);
    void DrawEditorBrush(ui::Texture*);
    void DrawEditorSelection();
    void BuildTileset(shared_ptr<FTileset>);

    /// Return the tileset texture for the specified level. Load the texture
    /// If necessary.
    ui::Texture* GetTilesetTexture(CLevel&);

    //shared_array<GLfloat> GetTileVertexArray(FSizeI size);
    //GLuint                GetTileVertexBuffer(FSizeI size);
    void                  ReleaseTileVertexBuffers();
    

    // Editor
    scoped_ptr<FRenderInfo> mEditorRenderInfo;

    KH_DECL_GC()

protected:
    //void LoadTilesee
    void UnloadTextures();
    bool ExecImpl(object cmd, object out);
    
    virtual bool OnTilesetTextureNotFound(
        shared_ptr<FTileset> const& tileset,
        shared_ptr<FTexture>& outResource
        );

    virtual bool OnTilesetTextureResourceInvalid(
        shared_ptr<FTileset> const& tileset,
        shared_ptr<FTexture>& outResource
        );

public:
    // Temporary public variables
    ui::Texture* TempActorTexture;
    shared_ptr<FTexture> TempActorTextureR;

    CClient(PyObject* self);
    virtual ~CClient();

    void Init(TPtr<CEngine> engine, object window);
    void Exit();
    virtual void Tick(float delta);
    void DrawViewport();
    bool Exec(object cmd, object out=object());
    virtual ui::MultiLineEditbox* GetConsoleTextBox();
    virtual void ClearConsole();
    virtual void PrintLine(str text);
    virtual void PrintLine(unicode text);
    FVector2F ConvertScreenToTileCoords(FVector2I const&);
    void SetMouseVisible(bool osstate, bool uistate);
    void InputMousePosition(int x, int y);
    void InputMouseButtonDown(ui::MouseButton b);
    void InputMouseButtonUp(ui::MouseButton b);
    void InputKeyDown(ui::Key::Scan keycode);
    void InputKeyUp(ui::Key::Scan keycode);
    void InputChar(ui::utf32 codepoint);
    bool IsKeyPressed(ui::Key::Scan keycode) const;

    TPtr<CEngine> GetEngine() const { assert (mEngine); return mEngine.Lock(); }
    bool GetInited() const { return mInited; }
    FVector2I const& GetMousePos() const { return mMousePos; }
    FSizeI const& GetViewportSize() const { return mViewportSize; }
    TPtr<ACamera> GetCamera() const { return mCamera.Lock(); }
    void SetCamera(TPtr<ACamera> const& value) { mCamera = value; }
    TPtr<ACamera> GetRenderCamera() const;
    void SetViewportSize(FSizeI const& size);
    uint32 GetWindowHandle() { return mWindowHandle; }
    void SetWindowHandle(uint32 handle);
    double GetTime() const { return mTime; }
    bool GetInitedUI() const { return mInitedUI; }

    // Renderer

    /// Load a texture from a file into a texture
    void LoadTexture(FTexture& dest, object path);
    /// Create a new texture with the specified name
    shared_ptr<FTexture> CreateTexture(str name);
    /// Create a new texture with the specified name from the specified file
    shared_ptr<FTexture> CreateTexture(str name, str filename);
    /// Retrieve texture with name or create a new one from the specified file
    shared_ptr<FTexture> GetTexture(str name, object filename=object());
    void DestroyTexture(shared_ptr<FTexture> texture);
    //void RegisterVertexBuffers(int num, GLuint* ids);
    //void UnregisterVertexBuffers(int num, GLuint* ids);

    bool GetShowActorBounds() const { return mShowActorBounds; }
    void SetShowActorBounds(bool value) { mShowActorBounds = value; }
    bool GetShowAllActors() const { return mShowAllActors; }
    void SetShowAllActors(bool value) { mShowAllActors = value; }
    bool GetForceDefaultSprite() const { return mForceDefaultSprite; }
    void SetForceDefaultSprite(bool value) { mForceDefaultSprite = value; }
    ui::Texture* GetTilesetTexture() const { return mTilesetTexture; }
    ui::Texture* GetErrorTexture();

    //-- Render Interface

    /// Draw a sprite on the viewport.
    /// Position is specified in tile coordates. Rect specifies the part of the
    /// texture, in pixels, that will be drawn.
    void DrawSprite(
        FVector3F const& pos,
        shared_ptr<FTexture> const& tex,
        FRectF const& rect=FRectF(),
        ui::colour const& color=ui::colour()
        );

    void DrawSprite(
        FVector3F const& pos,
        ui::Texture const* tex,
        FRectF const& rect=FRectF(),
        ui::colour const& color=ui::colour()
        );

    /// Draw a line on the viewport.
    /// This is only valid to call during rendering. Vertex positions are
    /// in tile coordaintes. Offset is a translation applied to all coordinates.
    void DrawLine(
        FVertex const* verts,
        uint count,
        FVector2F const& offset=FVector2F(),
        ui::colour const& color=ui::colour(1.0f, 1.0f, 1.0f, 1.0f)
        );

    // GUI
    ui::Window* CreateWindow(str type, str name);
    ui::Window* GetWindow(str name) const;
    bool IsWindowPresent(str name) const;
    void DestroyWindow(str name);
    void DestroyWindow(ui::Window* w);
    bool GetConsoleVisible() const;
    void SetConsoleVisible(bool value);

    bool OnTestButtonClicked(ui::EventArgs const& e);
    bool OnConsoleTextAccepted(ui::EventArgs const& e);
    bool OnConsoleCommand(str);
    bool OnKeyDown(ui::EventArgs const& e);
    void OnRenderViewDraw(FRenderView const& rv, ui::RenderingContext const& ctxt);

    KH_DEF_GET(bool, UIFocused)
    KH_DEF_GET(ui::Renderer*, UIRenderer)
    KH_DEF_GET(FResourceProvider*, UIResourceProvider)

    friend void _InitClientClasses();
    friend void _InitClientBindings();
    friend void _ExitClientClasses();
};

inline bool CClient::IsKeyPressed(ui::Key::Scan keycode) const {
    return mKeyPressTable[(uint8)keycode];
}


}} // namespace kharlia::engine

ENABLE_BACK_REFERENCE(kharlia::engine::CUIScriptModule)
ENABLE_BACK_REFERENCE(kharlia::engine::CClient)

#endif // _KH_ENGINE_CLIENT_H